package sketch.main;

import java.io.File;
import java.util.LinkedList;
import java.util.List;

import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;

import randoop.util.Files;
import sketch.ast.ASTUtils;
import sketch.instrumenter.Tracer;
import sketch.ounit.fuzz.ImprovedSketchGuidedRandomTestGeneration;
import sketch.ounit.fuzz.RandomPlacesExtractor;
import sketch.specs.SketchTestProcessor;
import sketch.specs.SketchTestProcessorBatch;
import sketch.util.Checker;
import sketch.util.Globals;
import sketch.util.IntermediateFiles;
import sketch.util.SketchUtil;

public class SketchTestMain {

	public static void main(String[] args) {
//		args = new String[]{"--test_sketch=./tests/sketch/issta11/specifyvalues/Z_ChooseOneValue_Example.java"};
		//args = new String[]{"--test_sketch=./tests/sketch/issta11/specifyoracles/Z_ObserverOracles_Example.java"};
		//process the command arguments, if the arguments is not legal
		//SketchTest will quit
		SketchTestOptions options = new SketchTestOptions();
		options.parseArgsAndConfigSketchTest(args);
		
		//generate tests
		SketchTestMain main = new SketchTestMain();
		if(SketchTestOptions.test_sketch != null) {
			//valid the test
			TestSketchValidator validator = new TestSketchValidator(SketchTestOptions.test_sketch);
			String[] errorMsg = validator.validate();
			if(errorMsg.length != 0) {
				SketchUtil.flushToStd(errorMsg);
				System.exit(1);
			}
			//if ok, start generating tests
		    main.generateTests();
		} else if (SketchTestOptions.test_sketch_dir != null) {
			//no random and symbolic is allowed here
			/*
			 * This may be seen as a limitation, but I think that is reasonable. For
			 * mass re-writing (not scratching from ground up) tests in practice,
			 * very few random/symbolic values are used.
			 * */
			main.generateTestsFromDir();
		}
		
		//delete all generated intermediate files
		if(SketchTestOptions.difiles) {
			IntermediateFiles.addAllFilesAsIntermediates(SketchTestOptions.output_dir, ".class");
		    IntermediateFiles.deleteIntermediateFiles();
		}
	}
	
	//all non-static method
	public void generateTests() {
		//set the mode to be release mode
		GeneratingTests.release = SketchTestOptions.release;
		//step 1: expand the notation
	    SketchTestProcessor sketchProcessor = new SketchTestProcessor(SketchTestOptions.test_sketch);
		CompilationUnit unit = sketchProcessor.getProcessedCompilationUnit();
		String inputFileName = sketchProcessor.getFileNameWithoutSuffix();
		
		//post step 1: write a temp directory
		String packageDirStructure = ASTUtils.convertPackageDeclToDir(unit.getPackage());
		String output_dir = SketchTestOptions.output_dir
		    + (packageDirStructure == null ? "" : Globals.fileSep + packageDirStructure) + Globals.fileSep;
		SketchUtil.createDir(output_dir);
		File fileAfterExpansion = ASTUtils.outputUnit(unit, output_dir + Globals.fileSep + sketchProcessor.fileName);
		System.out.println("Writing file: " + fileAfterExpansion.getAbsolutePath() + " after notation processing.");
		
		IntermediateFiles.addIntermediateFile(fileAfterExpansion);
		
		//step 2: process the random values
		//First check whether the unit contains any random values, if not skip it
		//else, call Sketch-guided random test generation
		//for those methods containing Observer, and symbolic values, need to add the *OUnit* annotation
		//otherwise, the following phase will not perform symbolic execution
		RandomPlacesExtractor extractor = new RandomPlacesExtractor(unit);
		List<File> filesToProcess = new LinkedList<File>();
		if(extractor.hasRandomValues()) {
			//invoke the random test generation to create random values
			Checker.checkTrue(SketchTestOptions.random_time > 0, "The random time should > 0, please set it by --random_time.");
			Checker.checkTrue(SketchTestOptions.fuzz_time > 0, "The fuzz time should > 0, please set it by --fuzz_time.");
			ImprovedSketchGuidedRandomTestGeneration generator =
				new ImprovedSketchGuidedRandomTestGeneration(fileAfterExpansion.getAbsolutePath(),
				SketchTestOptions.junit_name, SketchTestOptions.random_time, SketchTestOptions.fuzz_time);
			//generated a list of files
			List<File> createdFiles = generator.generate_tests();
			filesToProcess.addAll(createdFiles);
		} else {
			filesToProcess.add(fileAfterExpansion);
		}
		
		//add to the intermediate files
		for(File f : filesToProcess) {
			IntermediateFiles.addIntermediateFile(f);
		}

		System.out.println(Globals.lineSep + Globals.lineSep);
		int generated_file = 0;
		//step 3: process the symbolic value as well as observer methods
		for(File fileToProcess : filesToProcess) {
			CompilationUnit unitToProcess = ASTUtils.astRoot(fileToProcess);
			System.out.println("Processing file for symbolic values / observer: " + fileToProcess.getAbsolutePath());
			File generatedFile = GeneratingTests.genSymbolicValuesToOutputTests(unitToProcess, output_dir,
					SketchUtil.transJavaPathToFileName(fileToProcess) ,
					unit.getPackage() == null? null : unit.getPackage().getName().toString(), null/*extra imports*/,
					getSymbolicClass(), SketchTestOptions.symbolic_method);
			
			//remove that from intermediate files
			IntermediateFiles.retainIntermediateFile(generatedFile);
			
			//renaming all tests
			CompilationUnit renaming_unit = ASTUtils.astRoot(generatedFile);
			ASTUtils.renameAllTestsInCompilationUnit(renaming_unit);
			//rename the file name
			List<AbstractTypeDeclaration> types = renaming_unit.types();
			Checker.checkTrue(types.size() == 1, "The types size should be 1");
			String renamed_type = inputFileName + (generated_file++);
			types.get(0).getName().setIdentifier(renamed_type);
			String fileName = renamed_type + ".java";
			//System.out.println("fileName: " + fileName);
			if(!fileName.equals(generatedFile.getName())) {
				IntermediateFiles.addIntermediateFile(generatedFile);
			}
			String finalOutputPath = generatedFile.getParentFile().getAbsolutePath()
			    + Globals.fileSep + fileName;
			Files.writeToFileNoException(renaming_unit.toString(), finalOutputPath);
			
			System.out.println("Outputting final results: " + finalOutputPath);
		}
	}
	
	//XXX need to revise, this did not fully implement
	//expanding the random values
	public void generateTestsFromDir() {
       long t1 = System.currentTimeMillis();
		
		String src_dir = SketchTestOptions.test_sketch_dir;
		SketchTestProcessorBatch batchProcessor = new SketchTestProcessorBatch(src_dir);
		
		String output_dir = SketchTestOptions.dir_in_classpath;
		batchProcessor.generateTestsFromSketchesWithoutExecution(output_dir);
		System.out.println("start to do value instanitation...");
		String final_output = SketchTestOptions.output_dir;
		String entryJUnitClass = SketchTestOptions.entry_junit;
		Tracer.clearFile();
		GeneratingTestsBatch.obtainRuntimeInfoAndOutputTests(output_dir, final_output, entryJUnitClass, null);
		
		long elapsed = System.currentTimeMillis() - t1;
		if(SketchTestOptions.verbose) {
		  System.out.println("Total analysis time: " + elapsed + " millis.");
		}
	}
	
	//parse the compilation unit name
	private String getCompilationUnitName() {
		String whole_path = SketchTestOptions.test_sketch;
		int start_index = whole_path.lastIndexOf(Globals.fileSep);
		int end_index = whole_path.indexOf(".java");
		return whole_path.substring(start_index + 1, end_index);
	}
	
	private Class<?> getSymbolicClass() {
		if(SketchTestOptions.symbolic_class == null) {
			return null;
		}
		try {
			return Class.forName(SketchTestOptions.symbolic_class);
		} catch (ClassNotFoundException e) {
			throw new RuntimeException("The symbolic class you provided is not in classpath: "
					+ SketchTestOptions.symbolic_class);
		}
	}
}